﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Data.Common;

/*=====================================================================
  This file is part of a Microsoft SQL Server Shared Source Application.
  Copyright (C) Microsoft Corporation.  All rights reserved.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================= */


namespace Microsoft.SqlCommunity.FaultRetry
{
    /// <summary>
    /// This class is part of the custom .NET Data Provider which layers on top of the SQL Server data provider to provide 
    /// automatic failover on the client if a principal goes down.
    /// 
    /// This class is a layer over SqlCommand.  It retries command executions if the principal goes down.
    /// </summary>
    public class FaultRetryCommand : IDbCommand
    {
        private FaultRetryConnection _connection;  //The connection to the mirrored server
        private SqlCommand _command;           //The underlying command to execute

        // This is for MirrorDataAdapter use only!
        internal SqlCommand InnerCommand
        {
            get { return _command; }
        }

        /// <summary>
        /// This constructor is used when it is desirable to assign the connection after the command is created.
        /// </summary>
        public FaultRetryCommand()
        {
            _command = new SqlCommand();
        }

        /// <summary>
        /// This constructor is used when the Transact-SQL command text is available, and the connection will be assigned later.
        /// </summary>
        /// <param name="cmdText">The string which represents the Transact-SQL statement to execute.</param>
        public FaultRetryCommand(string cmdText)
        {
            _command = new SqlCommand(cmdText);
        }

        /// <summary>
        /// This constructor is used when the Transact-SQL command text and the connection are both available.
        /// </summary>
        /// <param name="cmdText">The string which represents the Transact-SQL statement to execute.</param>
        /// <param name="connection">Where to execute the command.</param>
        public FaultRetryCommand(string cmdText, FaultRetryConnection connection)
        {
            _command = new SqlCommand(cmdText, connection.InnerConnection);
            _connection = connection;
        }

        /// <summary>
        /// This constructor is used when the command text, connection, and transaction are available.
        /// </summary>
        /// <param name="cmdText">The string which represents the Transact-SQL statement to execute.</param>
        /// <param name="connection">Where to execute the command.</param>
        /// <param name="transaction">Under what transaction to execute the command.</param>
        public FaultRetryCommand(string cmdText, FaultRetryConnection connection, SqlTransaction transaction)
        {
            _command = new SqlCommand(cmdText, connection.InnerConnection, transaction);
            _connection = connection;
        }

        /// <summary>
        /// What command to execute.
        /// </summary>
        public string CommandText
        {
            get { return _command.CommandText; }
            set { _command.CommandText = value; }
        }

        /// <summary>
        /// How long to wait before assuming something is not working.
        /// </summary>
        public int CommandTimeout
        {
            get { return _command.CommandTimeout; }
            set { _command.CommandTimeout = value; }
        }

        /// <summary>
        /// What kind of command is being executed (text, sproc)
        /// </summary>
        public CommandType CommandType
        {
            /*
             * The sample only supports CommandType.Text.
             */
            get { return _command.CommandType; }
            set { _command.CommandType = value; }
        }

        /// <summary>
        /// Where to execute the command (what server/database using which credentials and network stack).
        /// </summary>
        public IDbConnection Connection
        {
            /*
             * The user should be able to set or change the connection at 
             * any time.
             */
            get { return _connection; }
            set
            {
                /*
                 * The connection is associated with the transaction
                 * so set the transaction object to return a null reference if the connection 
                 * is reset.
                 */
                if (_connection != value)
                    _command.Transaction = null;

                _connection = (FaultRetryConnection)value;
                _command.Connection = _connection.InnerConnection;
            }
        }

        /// <summary>
        /// The names, types, and values of variables to combine with the procedure call or command text to produce a executable
        /// procedure call or command.
        /// </summary>
        IDataParameterCollection IDbCommand.Parameters
        {
            get { return _command.Parameters; }
        }

        /// <summary>
        /// The names, types, and values of variables to combine with the procedure call or command text to produce a executable
        /// procedure call or command.  We don't overlay SqlParameters because there is nothing different for mirroring.
        /// </summary>
        public SqlParameterCollection Parameters
        {
            get { return _command.Parameters; }
        }

        /// <summary>
        /// Under what transaction the command will execute.
        /// </summary>
        public IDbTransaction Transaction
        {
            get { return _command.Transaction; }
            set { _command.Transaction = (SqlTransaction)value; }
        }

        /// <summary>
        /// Specifies how query command results are applied to the row being updated.
        /// </summary>
        public UpdateRowSource UpdatedRowSource
        {
            get { return _command.UpdatedRowSource; }
            set { _command.UpdatedRowSource = value; }
        }

        /// <summary>
        /// Terminate the execute of the command if possible.
        /// </summary>
        public void Cancel()
        {
            _command.Cancel();
        }

        /// <summary>
        /// Creates a parameter suitable for this command.
        /// </summary>
        /// <returns>A SqlParameter</returns>
        public IDbDataParameter CreateParameter()
        {
            return (IDbDataParameter)(new SqlParameter());
        }

        /// <summary>
        ///  ExecuteNonQuery is intended for commands that do
        ///  not return results, instead returning only the number
        ///  of records affected.
        /// </summary>
        /// <returns>Number of records affected</returns>
        public int ExecuteNonQuery()
        {

            // There must be a valid and open connection.
            if (_connection == null || _connection.State != ConnectionState.Open)
                throw new InvalidOperationException("Connection must valid and open");

            // Execute the command and return the number of records affected.
            // Invoke the SqlCommand wrapped with retry logic.
            return _connection.WithMirrorRetry<int>(() => _command.ExecuteNonQuery());

        }

        /// <summary>
        /// ExecuteReader retrieves results from the data source
        /// and returns a DataReader that allows the user to process
        /// the results.
        /// </summary>
        /// <returns>A data reader which can be used to obtain the results of the command.</returns>
        public IDataReader ExecuteReader()
        {
            // There must be a valid and open connection.
            if (_connection == null || _connection.State != ConnectionState.Open)
                throw new InvalidOperationException("Connection must valid and open");

            // Execute the command.
            // Invoke the SqlCommand wrapped with retry logic.
            return new FaultRetryDataReader(_connection.WithMirrorRetry<SqlDataReader>(() => _command.ExecuteReader()), _connection);
        }

        /// <summary>
        /// ExecuteReader retrieves results from the data source
        /// and return a DataReader that allows the user to process 
        /// the results.  The user specifies additional information about 
        /// how to execute the query and how it will affect the database.
        /// </summary>
        /// <param name="behavior">How to execute the query and how it will affect the database</param>
        /// <returns>The data reader for examining the results of the query.</returns>
        public IDataReader ExecuteReader(CommandBehavior behavior)
        {
            // There must be a valid and open connection.
            if (_connection == null || _connection.State != ConnectionState.Open)
                throw new InvalidOperationException("Connection must valid and open");

            // Invoke the SqlCommand wrapped with retry logic.
            return new FaultRetryDataReader(_connection.WithMirrorRetry<SqlDataReader>(() => _command.ExecuteReader(behavior)), _connection);

        }

        /// <summary>
        /// ExecuteScalar assumes that the command will return a single
        /// row with a single column, or if more rows/columns are returned
        /// it will return the first column of the first row.
        /// </summary>
        /// <returns>The value of the first column of the first row</returns>
        public object ExecuteScalar()
        {

            // There must be a valid and open connection.
            if (_connection == null || _connection.State != ConnectionState.Open)
                throw new InvalidOperationException("Connection must valid and open");

            // Invoke the SqlCommand wrapped with retry logic.
            return _connection.WithMirrorRetry<object>(() => _command.ExecuteScalar());

        }

        /// <summary>
        /// Create a prepared version of the command.
        /// </summary>
        public void Prepare()
        {
            _command.Prepare();
        }

        /// <summary>
        /// Implement the standard Dispose pattern
        /// </summary>
        void IDisposable.Dispose()
        {
            this.Dispose(true);
            System.GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Release any resources as part of disposing of this object, which in this case
        /// is the SqlCommand underlying this object.
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if (_command != null)
            {
                _command.Dispose();
                _command = null;
            }
        }
    }
}
